#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <memory>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>

#include <cstring>
#include <cstdlib>

#include "Log.hpp"
#include "InetAddr.hpp"
using namespace std;

class Socket;
using socket_sptr = shared_ptr<Socket>;

const static int gbacklog = 10;
enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR,
    LISTEN_ERROR
};

class Socket
{
public:
    // primitive method
    virtual void CreateSocketOrDie() = 0;
    virtual void BindSocketOrDie(InetAddr &addr) = 0;
    virtual void ListenSocketOrDie() = 0;
    virtual socket_sptr Accepter(InetAddr *out) = 0;
    virtual bool Connector(InetAddr &in) = 0;
    virtual int SockFd() = 0;

    // 待发送的数据类型为字符串
    virtual int Send(const string &str) = 0;
    // 接收的数据类型为字符串,收到数据为字符串,需要对其进行检测
    virtual int Recv(string* out)=0;

public:
    // template method
    void BuildListenSocket(InetAddr &addr)
    {
        CreateSocketOrDie();
        BindSocketOrDie(addr);
        ListenSocketOrDie();
    }
    bool BuildClientSocket(InetAddr &addr)
    {
        CreateSocketOrDie();
        return Connector(addr);
    }
};

class TcpSocket : public Socket
{
public:
    TcpSocket(int fd = -1)
        : _sockfd(fd)
    {
    }

    void CreateSocketOrDie() override
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "create socket error");
            exit(SOCKET_ERROR);
        }
        LOG(DEBUG, "create socket success,sockfd:%d", _sockfd);
    }

    // 绑定需要本地主机的IP地址与端口号,需要外部传入
    void BindSocketOrDie(InetAddr &addr) override
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(addr.Port());
        local.sin_addr.s_addr = inet_addr(addr.IP().c_str());

        int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "bind success,sockfd:%d", _sockfd);
    }

    void ListenSocketOrDie() override
    {
        int n = ::listen(_sockfd, gbacklog);
        if (n < 0)
        {
            LOG(FATAL, "listen error");
            exit(LISTEN_ERROR);
        }
        LOG(DEBUG, "listen success,sockfd:%d", _sockfd);
    }

    // 服务端获取客户端连接,外部需要客户端的IP地址与port端口号发送数据
    socket_sptr Accepter(InetAddr *out) override
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        socklen_t len = sizeof(peer);
        int sockfd = ::accept(_sockfd, (struct sockaddr *)&peer, &len);
        if (sockfd < 0)
        {
            LOG(WARNING, "accept error");
            return nullptr;
        }
        *out = peer;
        // 外部发送数据时,需要新的文件描述符sockfd
        // 创建TcpSocket对象,构造时新创建的文件描述符,给外部返回TcpSocket对象的指针,附带提供SockFd()接口
        socket_sptr sock(new TcpSocket(sockfd));
        return sock;
    }
    int SockFd() override
    {
        return _sockfd;
    }

    // 客户端发送连接请求,客户端需要服务端的IP地址与端口号
    // 客户端不需要显示绑定,当用户发起连接时由操作系统绑定,绑定&&发起连接成功返回true,否则返回false
    bool Connector(InetAddr &in) override
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(in.Port());
        peer.sin_addr.s_addr = inet_addr(in.IP().c_str());
        int n = ::connect(_sockfd, (struct sockaddr *)&peer, sizeof(peer));
        if (n < 0)
        {
            LOG(FATAL, "connect error");
            return false;
        }
        return true;
    }

    int Send(const string &str) override
    {
        ssize_t n=::send(_sockfd,str.c_str(),str.size(),0);
        return n;
    }
    
    int Recv(string* out) override
    {
        char inbuffer[1024];
        int n=::recv(_sockfd,inbuffer,sizeof(inbuffer),0);
        if(n>0)
        {
           inbuffer[n]=0;
           *out+=inbuffer; 
        }
        return n;
    }

    ~TcpSocket()
    {
    }

private:
    int _sockfd;
};